package network;
/**
 * @author Gianni Gambetti
 */
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.ArrayList;

import core.Phrox;
import encryption.EncryptionPlugin;

public class Old_Server {

	public static final Message KICK_MSG = new Message(Message.TEXT_MSG,"You have been kicked.");
	public static final Message BAN_MSG = new Message(Message.TEXT_MSG,"You have been banned.");
	public static final Message SERVER_CLOSE = new Message(Message.TEXT_MSG, "Server shut down in 5 seconds.");
	public static final Message BLANK = new Message(Message.UNDEFINED_TYPE,""); 

	private EncryptionPlugin enc;
	private int port;
	private ServerSocket socket;

	private long startTime=0;

	private ArrayList<Connection> connections = new ArrayList<Connection>();
	private ArrayList<Old_ServerThread> threads = new ArrayList<Old_ServerThread>();
	//set this to false when you no longer want to open new connections
	public boolean listening=true;

	private ArrayList<User> users = new ArrayList<User>();
	private ArrayList<InetAddress> banned = new ArrayList<InetAddress>();

	//set this to true to get the server to shut down.
	public boolean close=false;


	public Old_Server(int port){
		this(port,null);
	}

	public Old_Server(int port, EncryptionPlugin en){

		startTime = System.currentTimeMillis()/1000;//seconds.
		//start%60 = seconds
		//(start/60)%60 = minutes
		//(start/3600)%24 = hours
		//(start/86400) = days

		enc = en;
		this.port = port;
	}



	/**
	 * The main server loop, DOES NOT RETURN until the server is off.
	 * @param port the port on which the connection will occur.
	 * @param encrypt encrypt connection?
	 * @param method encryption type.
	 */
	public void startServer(){
		try {
			socket = new ServerSocket(port);
		} catch (IOException e) {
			e.printStackTrace();
			//cant listen on port
		}finally{
			if(socket==null){
				core.Phrox.outln("ERROR");
				core.Phrox.outln("Couldn't start server. Exiting");
				System.exit(1);
			}
		}
		//Scanner scan = new Scanner(System.in);


		while(listening){

			try {
				Connection c = new Connection(enc);
				c.socket = this.socket.accept();
				c.socket.setKeepAlive(true);
				boolean added = false;
				boolean failed = false;
				//connections.add(c);
				for(Old_ServerThread st : threads){
					if(!st.isFull()){//if it's not full
						int i = st.addConnection(c);
						if(i != 0){
							failed=true;
						}
						added=true;
						break;
					}
				}

				if(!added){
					//wasn't added to any of the threads running
					//all threads are assumed full.

					Old_ServerThread st = new Old_ServerThread(this);
					threads.add(st);
					if(!st.isFull()){//this is a new thread, therefore is empty.
						int i = st.addConnection(c);
						if(i!=0){
							failed=true;
						}
					}
					st.start();

				}
				if(!failed){
					connections.add(c);
				}

			} catch (IOException e) {
				e.printStackTrace();
			}


		}
		try {
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}



	}

	/**
	 * Close the server, don't open new connections, close the old ones.
	 */
	public void close(){
		listening = false;
		close = true;


	}

	/**
	 * @return returns the number off current connections with clients
	 */
	public int numConnection(){
		return connections.size();
	}

	/**
	 * @return returns the number of server threads running for the server
	 */
	public int numThreads(){
		return threads.size();
	}

	public boolean removeThread(Old_ServerThread s){
		return threads.remove(s);
	}

	/**
	 * @param str command to run
	 * @return String, is the text the command returns
	 * @throws NotACommandException 
	 */
	public String serverCommands(String str) throws NotACommandException{
		String ret = "";
		str = str.toLowerCase().trim();
		if(str.length()>0)
			if(str.charAt(0) == '/'){
				if(str.startsWith("/kick")){

					String command;
					try {
						command = str.substring(str.indexOf(" ")+1).trim();
					} catch (Exception e) {
						command = "";
					}

					User u = findUser(command);

					for(Connection c : connections){
						if(c.user.equals(u)){
							try {
								Old_ServerThread.send(c,Old_Server.KICK_MSG);
								Message m = new Message(Message.TEXT_MSG, ""+u.getName()+" has been kicked.");//TODO format message
								sendAll(m);
							} catch (DisconnectionException e) {}
							disconnect(c);
							ret = "Kicked succesfully.";
							break;
						}
					}


				}else if(str.contains("/banned")){
					if(banned.size() <= 0){
						ret = "No one has been banned";
					}else{
						ret+= "--Banned--";
						for(InetAddress u : banned){
							ret += "\n"+u.getHostName()+":"+u.getHostAddress();
						}
					}
				}else if( str.contains("/ban") ){

					String command;
					try {
						command = str.substring(5).trim();
					} catch (Exception e) {
						command = "";
					}
					if(command.length() < 6){
						return serverCommands("/banned");
					}
					try {
						InetAddress u = InetAddress.getByName(command);
						banned.add(u);

						User usr = findUser(command);

						for(Connection c : connections){
							if(c.user.equals(usr)){
								try {
									Old_ServerThread.send(c,Old_Server.BAN_MSG);
								} catch (DisconnectionException e) {}
								break;
							}
						}

						ret = "Succesfully banned.";
						serverCommands("/kick "+u.getHostAddress());
						System.out.println("Banned:"+u.getHostAddress());
					} catch (UnknownHostException e) {
						ret = "Couldn't ban ip:"+command;
					}

				}else if(str.contains("/unban")){

					String command;
					try {
						command = str.substring(7).trim();
					} catch (Exception e) {
						command = "";
					}
					if(command.equalsIgnoreCase("all")){
						banned.clear();
						System.out.println("All Unbanned.");
						ret = "All users unbanned.";
						
					}else if(command.length() < 6){
						ret = serverCommands("/banned");
					}else{
						InetAddress u = findBanned(command);
						banned.remove(u);
						System.out.println("Unbanned:"+u.getHostAddress());
						ret = u.getHostAddress()+" unbanned.";
					}

				}else if(str.contains("/members")){
					ret += "-MEMBERS:";
					for(User u : users){
						ret += "\n"+u.getIp()+":"+u.getName();
					}
				}else if(str.contains("/time")){

					long total = System.currentTimeMillis()/1000 - startTime;

					byte minutes = (byte) ((total/60)%60);
					byte hours = (byte) ((total/3600)%24);
					int days = (int) (total/86400);
					ret="Run Time: d"+days+" h"+hours+" m"+minutes;
					System.out.println(ret);

				}else if(str.contains("/ping")){

					String command;
					try {
						command = str.substring(str.indexOf(" ")+1).trim();
					} catch (Exception e) {
						command = "";
					}
					if(command.equals("all")){
						//ping all users :)
						for(Connection c : connections){
							System.out.println("Pinging:"+c.user.getIp()+"");
							Message pi = new Message(Message.PINGR,System.currentTimeMillis()+"");
							try {
								Old_ServerThread.send(c,pi);
							} catch (DisconnectionException e) {}
						}
					}else{
						User u = findUser(command);
						for(Connection c : connections){
							if(c.user.equals(u)){
								System.out.println("Pinging:"+c.user.getName()+c.user.getIp()+"");
								Message pi = new Message(Message.PINGR,System.currentTimeMillis()+"");
								try {
									Old_ServerThread.send(c,pi);
								} catch (DisconnectionException e) {}
								break;
							}
						}
					}


				}else if(str.contains("/help")){
					ret ="Commands for the server are:\n" +
					"/kick\t[ip]\t\tkick user using ip address [ip]\n" +
					"/kick\t[name]\t\tkick user using name [name]\n" +
					"/ban\t[ip]\t\tadds people to the ban list\n" +
					"/unban\t[ip]\t\tremoves people from the ban list\n" +
					"\tall\t\tremoves everyone from ban list\n" +
					"/banned\t\t\tprints a list of all banned persons\n" +
					"/members\t\t\tprints a list of all members\n" +
					"/time\t\t\tshows server uptime\n" +
					"/cls\t\t\tclears the text area\n" +
					"/info\t\t\tdisplays external IP and version info";
				}else if(str.equalsIgnoreCase("/cls")){
					Phrox.gui_ClearText();
				}else if(str.equals("/info")){
					String s;
					try {
						s = Phrox.os;
						if(s.contains("windows")){
							s = InetAddress.getLocalHost().getHostName();
						}else if(s.contains("linux")){
							s = InetAddress.getLocalHost().getHostName()+".local";
						}
						s = InetAddress.getByName(s).getHostAddress();
					} catch (UnknownHostException e) {
						s = "UNKNOWN";
						e.printStackTrace();
					}
					ret = "External IP: "+ Phrox.getIP() +"\n" +
					"Lan IP: "+s+"\n"+
					"Version: "+Phrox.version+"\n" +
					"Name: "+Phrox.name+"\n"+
					"Port: "+NetworkVars.socketNum;
				}else{
					ret = null;
				}
			}
		if(ret == null){
			throw new NotACommandException(""+str);
		}
		if(NetworkVars.debug){
			System.out.println("========\n"+ret+"\n========");
		}
		return ret;
	}

	public boolean addUser(User u){//return false if user is banned
		if(u != null && u.getIp() != null && u.getName() != null){

			for(InetAddress user : banned){
				if(u.getIp().equals(user)){
					return false;
				}
			}
			users.add(u);
		}
		return true;
	}

	/**
	 * 
	 * @param inet the inetaddress of the user
	 * @param b if the user has been banned
	 * @return returns the user object
	 */
	private User findUser(String inet){//,boolean b){
		User ret = null;
		inet = inet.toLowerCase();
		//System.out.println(inet);
		String str="";
		for(User u : users){
			str = u.toString().toLowerCase();
			if(str.contains(inet)){
				ret = u;
			}
		}


		return ret;
	}

	public InetAddress findBanned(String inet){
		InetAddress ret = null;
		inet = inet.toLowerCase();
		//System.out.println(inet);
		String str="";
		for(InetAddress in : banned){
			str = in.toString().toLowerCase();
			if(str.contains(inet)){
				ret = in;
			}
		}


		return ret;
	}

	public boolean isBanned(InetAddress inet){
		boolean ret = false;
		//System.out.println(inet);
		for(InetAddress in : banned){
			if(in.equals(inet)){
				ret = true;
			}
		}


		return ret;
	}

	@SuppressWarnings("unchecked")
	public ArrayList<InetAddress> getBanned(){
		return (ArrayList<InetAddress>)banned.clone();
	}

	public boolean ban(InetAddress inet){
		return banned.contains(inet) || banned.add(inet);
	}

	public String getUserList(){
		String ret = "";
		for(User u : users){
			ret += "\n-"+u.getName();
		}
		ret = "-MEMBERS:"+ret;
		return ret;
	} 



	/*
	 * BELOW IS FOR THE SINGLE THREAD-MULTI CONNECTION
	 */

	public boolean sendAll(Message m){
		Phrox.outln(m.getText());
		for(Old_ServerThread st : threads){
			st.sendAll(m);
		}
		return true;
	}


	public void disconnect(Connection c){
		c.flagDisconnect = true;
		c.localDisconnect = false;
	}

	public void remove(Connection c){
		removeUser(c.user);
		removeConnection(c);
	}
	
	public void removeUser(User u){
		users.remove(u);
	}
	
	/**
	 * 
	 * @param c connection to remove
	 * @return returns true after removing the connection
	 */
	public boolean removeConnection(Connection c){
		connections.remove(c);
		for(Old_ServerThread st : threads){
			if(st.removeConnection(c)){
				return true;
			}
		}
		return false;
	}

}
